/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       button.cpp

	$Header: /game/button.cpp $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "button.h"

#include "adventure_sounds.h"
#include "bitmap_layer_window.h"
#include "button_bitmaps.h"
#include "mouse_event.h"
#include "paint_surface.h"
#include "platform.h"
#include "standard_fonts.h"
#include "text_window.h"


// --------------------------------------------------------
// button body
// --------------------------------------------------------
t_button::t_button( t_cached_ptr<t_button_bitmaps>& bitmaps, t_screen_point point, 
				    t_window* parent, std::string const& text )
        : t_window( k_no_transparency, parent ), m_bitmaps( bitmaps )
{
    m_pressed = false;
    m_disabled = false;
    m_highlighted = false;
	m_current_image = 0;

	if (bitmaps != 0)
	{
		if (bitmaps->get_up_bitmap() != 0)
			m_released_image = new t_bitmap_layer_window( bitmaps->get_up_bitmap(),
			                                              t_screen_point(0,0), this );
		m_released_rect = bitmaps->get_up_rect();

		if (bitmaps->get_down_bitmap() != 0)
			m_pressed_image = new t_bitmap_layer_window( bitmaps->get_down_bitmap(),
			                                             t_screen_point(0,0), this );

		m_pressed_rect = bitmaps->get_down_rect();

		if (bitmaps->get_highlighted_bitmap() != 0)
			m_highlighted_image = new t_bitmap_layer_window( bitmaps->get_highlighted_bitmap(),
			                                                 t_screen_point(0,0), this );

		m_highlighted_rect = bitmaps->get_highlighted_rect();

		if (bitmaps->get_highlighted_pressed_bitmap() != 0)
			m_highlighted_pressed_image = new t_bitmap_layer_window( bitmaps->get_highlighted_pressed_bitmap(),
			                                                 t_screen_point(0,0), this );

		m_highlighted_pressed_rect = bitmaps->get_highlighted_pressed_rect();

		if (bitmaps->get_disabled_bitmap() != 0)
			m_disabled_image = new t_bitmap_layer_window( bitmaps->get_disabled_bitmap(),
			                                              t_screen_point(0,0), this );

		m_disabled_rect = bitmaps->get_disabled_rect();
	}
	
	t_screen_rect rect = compute_size();

	update_transparency();
	rect.left = 0;
	rect.top = 0;
	init( rect + point );
	if (!text.empty())
		add_text( text, get_font( rect.height() ) );
	set_image();
};

// --------------------------------------------------------
// create button with no images
// --------------------------------------------------------
t_button::t_button( t_screen_point point, t_window* parent )
        : t_window( k_no_transparency, parent )
{
    m_pressed = false;
    m_disabled = false;
    m_highlighted = false;
	m_current_image = 0;

	init( t_screen_rect(point.x, point.y, point.x, point.y ) );
}

// --------------------------------------------------------
// check if a screen point is "owned" by this button
// --------------------------------------------------------
bool t_button::is_contained( t_screen_point point ) const
{
	if (!is_point_in_rect( point, get_client_rect() ))
		return false;
	if (!m_has_transparency)
		return true;
	if (m_current_image == 0)
		return false;

	t_window_list::const_reverse_iterator it;
	t_screen_point screen_point = to_screen( point );

	// We're going to check ALL our children when evaluating if we want to claim
	// a point so that when the button changes state (like when it is clicked)
	// the answer doesn't suddenly change.
	for (it = get_children_rbegin(); it != get_children_rend(); it++)
	{
		if ( (*it)->is_contained( (*it)->to_client( screen_point ) ) )
			return true;
	}

	return false;
}

// -----------------------------------------------------------------------
// find window from screen coordinates
// -----------------------------------------------------------------------
t_window* t_button::get_child( t_screen_point point, bool ignore_visibility ) // find window from client coordinates
{
	if (!is_visible() && !ignore_visibility)
		return 0;
	if (!is_contained( point ))
		return 0;

	t_window_list::const_reverse_iterator it;
	t_window*      result;
	t_screen_point screen_point = to_screen( point );

	// Since we passed the "is_contained" test, we're pretty sure we'll find
	// something.  That being the case, we're willing to spend the time to check
	// for a visible hit first, then fallback to a hit on an invisible child.
	// If we needed to speed things up we could just do the "ignore" test right
	// off and put up with possibility of getting an invisible child when a visible
	// one was available.
	for (it = get_children_rbegin(); it != get_children_rend(); it++)
	{
		result = (*it)->get_child( (*it)->to_client( screen_point ), false );
		if (result != 0)
			return result;
	}
	// Now look for an invisible hit
	for (it = get_children_rbegin(); it != get_children_rend(); it++)
	{
		result = (*it)->get_child( (*it)->to_client( screen_point ), true );
		if (result != 0)
			return result;
	}

	// I'm not sure we'll ever get here given the way is_contained is implemented
	// and the fact that we checked that first off, but just in case...
	if (m_has_transparency == k_completely_transparent)
		return 0;
	return this;
}

// --------------------------------------------------------
// handle a left button down
// --------------------------------------------------------
void t_button::left_button_down( t_mouse_event const& event )
{
    if (m_pressed || m_disabled)
        return;
    m_pressed = true;
	set_image();
	get_dialog_sound( k_dialog_sound_button )->play( get_sound_volume() );
	m_down_clicked( this );
}

// --------------------------------------------------------
// handle a left button release
// --------------------------------------------------------
void t_button::left_button_up( t_mouse_event const& event )
{
    if (!m_pressed)
       return;
    m_pressed = false;
	set_image();
	if (!m_disabled)
		m_clicked( this );
}

// --------------------------------------------------------
// handle a right button release
// --------------------------------------------------------
void t_button::right_button_up( t_mouse_event const& event )
{
	if (m_right_clicked.get() == 0)
		t_window::right_button_up( event );
	else
		m_right_clicked( this );
}

// --------------------------------------------------------
// handle a double click
// --------------------------------------------------------
void t_button::left_double_click( t_mouse_event const& event )
{
	// important to call this, because this is a virtual function and is
	// needed by toggle buttons in particular.  
	// THERE IS A PROBLEM here, though.  This causes multiple "click"
	// sound effects to be heard in some cases because there is a left
	// click event first, then the double click event (which chains to left
	// left click again).
	left_button_down( event );

	m_double_clicked( this );
}

// --------------------------------------------------------
// handle mouse leaving this window
// --------------------------------------------------------
void t_button::mouse_leaving( t_window* old_window, t_window* new_window,
							  t_mouse_event const& event )
{
	if (is_child( new_window ))
		return;

	if (m_pressed || m_highlighted)
	{
		m_pressed = false;
	    m_highlighted = false;
		set_image();
	}
}

// --------------------------------------------------------
// handle mouse moving over this window
// --------------------------------------------------------
void t_button::mouse_move( t_mouse_event const& event )
{
    if (m_disabled || m_highlighted)
        return;

	// don't respond to move events which are obsolete.
	if (event.screen_point != get_mouse_screen_position())
		return;

	// Don't respond when drag & drop is going on since we aren't generally a valid target
	if (t_window::get_drag_object())
		return;

    m_highlighted = true;
	set_image();
}

// --------------------------------------------------------
// compute the button's size.
// --------------------------------------------------------
t_screen_rect t_button::compute_size()
{
	t_window_list::iterator index = get_children_begin();
	t_screen_rect           rect(0,0,0,0);

	// exit if there are no children.
	if (index == get_children_end())
		return rect;

	// find the total screen rectangle occupied by all children
	rect = (*index)->get_screen_rect();
	index++;
	while (index != get_children_end())
	{
		rect = get_extent( (*index)->get_screen_rect(), rect );
		index++;
	}
	return rect;
}

// --------------------------------------------------------
// compute whether we need to be transparent.
// --------------------------------------------------------
void t_button::update_transparency()
{
	// determine if we need to mark this as "having transparency".
	t_window_list::iterator index = get_children_begin();

	if (index == get_children_end())
		return;

	t_screen_rect rect = (*index)->get_screen_rect();

	m_has_transparency = k_no_transparency;
	while (index != get_children_end())
	{
		if ((*index)->get_screen_rect() != rect 
			|| (*index)->get_transparency() != k_no_transparency)
		{
			m_has_transparency = k_completely_transparent;
			break;
		}
		index++;
	}
}

// --------------------------------------------------------
// draw this window
// --------------------------------------------------------
// re-size the button to fit the child windows.
void t_button::update_size()
{
	if (!is_open())
		return;

	// get our own screen rect, and force the origin of the rect to match.
	t_screen_rect rect = compute_size();

	update_transparency();
	
	// remap rectangle to parent's client coordinates, and change size.
	rect = get_parent()->to_client( rect );
	move( rect, false );
}

// --------------------------------------------------------
// determine which child should be visible.
// --------------------------------------------------------
void t_button::set_image()
{
	t_window* old_image = m_current_image;

	m_current_image = 0;

	if (m_disabled && m_disabled_image != 0)
		m_current_image = m_disabled_image;
	else
	if (m_pressed && m_highlighted && m_highlighted_pressed_image != 0)
		m_current_image = m_highlighted_pressed_image;
	else if (m_pressed && m_pressed_image != 0)
		m_current_image = m_pressed_image;
	else if (m_highlighted && m_highlighted_image != 0)
	{
		m_current_image = m_highlighted_image;
	}
	else
		m_current_image = m_released_image;

	if (old_image != m_current_image)
		invalidate();

	// set only images set in above variables - do not affect other children.
	if (m_disabled_image != 0)
		m_disabled_image->set_visible( m_disabled_image == m_current_image );
	if (m_pressed_image != 0)
		m_pressed_image->set_visible( m_pressed_image == m_current_image );
	if (m_highlighted_image != 0)
		m_highlighted_image->set_visible( m_highlighted_image == m_current_image );
	if (m_highlighted_pressed_image != 0)
		m_highlighted_pressed_image->set_visible( m_highlighted_pressed_image == m_current_image );
	if (m_released_image != 0)
		m_released_image->set_visible( m_released_image == m_current_image );
}

// --------------------------------------------------------
// button
// set highlighted state
// --------------------------------------------------------
void t_button::set_highlighted( bool arg )
{
	if ( arg == m_highlighted )
		return;
	m_highlighted = arg;
	set_image();
}

// --------------------------------------------------------
// button
// set pressed state
// --------------------------------------------------------
void t_button::set_pressed( bool arg )
{
	if ( arg == m_pressed )
		return;
	m_pressed = arg;
	set_image();
}

// --------------------------------------------------------
// add text to a button.
// --------------------------------------------------------
void t_button::add_text( std::string const& text, t_cached_font& font, 
						 t_pixel_24 const& color, t_pixel_24 const& highlight_color,
						 t_pixel_24 const& disabled_color, bool drop_shadow,
						 t_pixel_24 const& drop_shadow_color )
{
	t_screen_rect rect;

	assert( m_released_image != 0);
	rect = m_released_image->get_screen_rect();

	if (m_disabled_image != 0)
	{
		if( m_disabled_rect.width() > 0 )
		{
			rect = m_disabled_image->to_screen( m_disabled_rect );
			font = get_font( rect.height() );
		}
		m_disabled_text_window = new t_text_window( font, m_disabled_image->to_client(rect),
			                             m_disabled_image, text.c_str(), disabled_color );
		m_disabled_text_window->set_drop_shadow( drop_shadow, drop_shadow_color );
		m_disabled_text_window->set_center_vertical();
		m_disabled_text_window->set_center_horizontal();
	}
	if (m_released_image != 0)
	{
		if( m_released_rect.width() > 0 )  
		{
			rect = m_released_image->to_screen( m_released_rect );
			font = get_font( rect.height() );
		}
		m_released_text_window = new t_text_window( font, m_released_image->to_client(rect),
			                             m_released_image, text.c_str(), color );
		m_released_text_window->set_drop_shadow( drop_shadow, drop_shadow_color );
		m_released_text_window->set_center_vertical();
		m_released_text_window->set_center_horizontal();
	}
	if (m_highlighted_image != 0)
	{
		if( m_highlighted_rect.width() > 0 )
		{
			rect = m_highlighted_image->to_screen( m_highlighted_rect );
			font = get_font( rect.height() );
		}
		m_highlighted_text_window = new t_text_window( font, m_highlighted_image->to_client(rect),
			                             m_highlighted_image, text.c_str(), highlight_color );
		m_highlighted_text_window->set_drop_shadow( drop_shadow, drop_shadow_color );
		m_highlighted_text_window->set_center_vertical();
		m_highlighted_text_window->set_center_horizontal();
	}
	if (m_highlighted_pressed_image != 0)
	{
		if( m_highlighted_pressed_rect.width() > 0 )
		{
			rect = m_highlighted_pressed_image->to_screen( m_highlighted_pressed_rect );
			font = get_font( rect.height() );
		}
		m_highlighted_pressed_text_window = new t_text_window( font, m_highlighted_pressed_image->to_client(rect),
			                             m_highlighted_pressed_image, text.c_str(), highlight_color );
		m_highlighted_pressed_text_window->set_drop_shadow( drop_shadow, drop_shadow_color );
		m_highlighted_pressed_text_window->set_center_vertical();
		m_highlighted_pressed_text_window->set_center_horizontal();
	}
	if (m_pressed_image != 0)
	{
		if( m_pressed_rect.width() > 0 )
		{
			rect = m_pressed_image->to_screen( m_pressed_rect );
			font = get_font( rect.height() );
		}

		m_pressed_text_window = new t_text_window( font, 
			                             m_pressed_image->to_client(rect),
			                             m_pressed_image, text.c_str(), color );
		m_pressed_text_window->set_drop_shadow( drop_shadow, drop_shadow_color );
		m_pressed_text_window->set_center_vertical();
		m_pressed_text_window->set_center_horizontal();
	}
}

void t_button::set_text( std::string const& text )
{
    assert (m_released_text_window);
    assert (m_pressed_text_window);
    
	if (m_highlighted_image != 0)
    	m_highlighted_text_window->set_text( text.c_str() );
	if (m_disabled_image != 0)
    	m_disabled_text_window->set_text( text.c_str() );
	if (m_pressed_image != 0)
    	m_pressed_text_window->set_text( text.c_str() );
	if (m_released_image != 0)
	    m_released_text_window->set_text( text.c_str() );
    
}

// --------------------------------------------------------
// create a button from a set of bitmaps
// --------------------------------------------------------
void set_button_layers( t_button* button, t_bitmap_group_ptr bitmaps, std::string name )
{
	t_window*             window;
	t_bitmap_layer const* layer;
	t_screen_rect         rect;
	t_screen_point        button_origin;
	t_screen_point        origin;

	layer = bitmaps->find( name + "_released" );
	if (layer == 0)
		return;

	rect = layer->get_rect();
	button_origin = button->get_screen_rect().top_left() - rect.top_left();
	window = new t_bitmap_layer_window( layer, -rect.top_left(), button );
	button->set_released_image( window );
	layer = bitmaps->find( name + "_pressed" );
	if (layer != 0)
	{
		origin = button_origin - button->get_screen_rect().top_left();
		window = new t_bitmap_layer_window( layer, origin, button );
		button->set_pressed_image( window );
	}
	layer = bitmaps->find( name + "_disabled" );
	if (layer != 0 )
	{
		origin = button_origin - button->get_screen_rect().top_left();
		window = new t_bitmap_layer_window( layer, origin, button );
		button->set_disabled_image( window );
	}
	layer = bitmaps->find( name + "_highlighted" );
	if (layer != 0 )
	{
		origin = button_origin - button->get_screen_rect().top_left();
		window = new t_bitmap_layer_window( layer, origin, button );
		button->set_highlighted_image( window );
	}
	layer = bitmaps->find( name + "_highlighted_pressed" );
	if (layer != 0 )
	{
		origin = button_origin - button->get_screen_rect().top_left();
		window = new t_bitmap_layer_window( layer, origin, button );
		button->set_highlighted_pressed_image( window );
	}
}

// --------------------------------------------------------
// create a button from a set of bitmaps
// --------------------------------------------------------
t_button* create_button( t_bitmap_group_ptr bitmaps, std::string name, t_window* parent,
						 t_screen_point origin )
{
	t_button*		      button;
	t_bitmap_layer const* layer;
	t_screen_rect         rect;

	layer = bitmaps->find( name + "_released" );
	if (layer == 0)
		return 0;

	rect = layer->get_rect();
	button = new t_button( rect.top_left() - origin, parent );
	set_button_layers( button, bitmaps, name );
	return button;
}
